package cn.xumaoyang.user.service.impl;

import cn.xumaoyang.basic.exception.BusinessException;
import cn.xumaoyang.basic.service.impl.BaseServiceImpl;
import cn.xumaoyang.basic.util.*;
import cn.xumaoyang.user.domain.Logininfo;
import cn.xumaoyang.user.domain.User;
import cn.xumaoyang.user.domain.WxUser;
import cn.xumaoyang.user.dto.Logdto;
import cn.xumaoyang.user.dto.Userdto;
import cn.xumaoyang.user.mapper.LogininfoMapper;
import cn.xumaoyang.user.mapper.UserMapper;
import cn.xumaoyang.user.mapper.WxUserMapper;
import cn.xumaoyang.user.service.ILogininfoService;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LogininfoServiceImpl extends BaseServiceImpl<Logininfo> implements ILogininfoService {

    @Autowired
    private LogininfoMapper logininfoMapper;


    @Autowired
    private RedisTemplate redisTemplate;


    @Autowired
    private WxUserMapper wxUserMapper;


    @Autowired
    private UserMapper userMapper;

    //账号密码登录。
    @Override
    public Map<String, Object> accountLogin( Logdto logdto) {
        //判断账号密码不能为空，这个是为空。
        if (StringUtils.isEmpty(logdto.getPassword())
            || StringUtils.isEmpty(logdto.getUsername())
            || logdto.getType() == null){
            throw new BusinessException("账号或密码为必填项，请填写");
        }
        //判断账号是否已经注册。
        Logininfo logininfo = logininfoMapper.loadByUserName(logdto.getUsername());
        if(logininfo == null){
            throw new BusinessException("不存在该账号请先注册");
        }
        //查询是否被禁用。
        if(logininfo.getDisable() == 0){
            throw new BusinessException("该用户已经被禁用，如申诉请走申诉通道");
        }
        //获取盐值，因为数据库里面的密码是加密过的，必须和数据库里面的密码一致。
        String salt = logininfo.getSalt();//获取数据库里的盐值。
        //MD5加密，前端传来的密码加上颜值等于当前密码。
        String s = MD5Utils.encrypByMd5(logdto.getPassword() + salt);
        //判断密码，不相等。
        if(!logininfo.getPassword().equals(s)){
            throw new BusinessException("账户或者密码输入错误");
        }
        //密码一致把数据保存到redis.
        //UUID唯一标识符，全世界都不会重复，保证token不重复，后端拦截要使用。
        String token  = UUID.randomUUID().toString();
        //把数据保存到redis。
        redisTemplate.opsForValue().set(
                token,   //token随机串，因为登录拦截要使用。。
                logininfo, //登录用户的信息，这里如果传的是对象就得实现序列化，Serializable。
                30,   //时间
                TimeUnit.MINUTES //时间类型，分钟。
        );
        //获取Map
        Map<String,Object> map = new HashMap<>();
        //给token设置Key和值
        map.put("token",token);
        //================我们有些数据不希望前端看到，所以设置为Null再返回到前端。
        logininfo.setPassword(null);
        logininfo.setSalt(null);
        //给用户数据设置key和值。
        map.put("logininfo",logininfo);
        return map;
    }





    //微信扫码登录。
    @Override
    public AjaxResult wechat(Map<String, String> param) {
        //第一步拿到用户扫码之后的code。
        String code = param.get("code");
        //如果code为空那就直接报错。
        if (StringUtils.isEmpty(code)){
            throw new BusinessException("系统异常请重新扫码");
        }


        //第二步更改code所需获得token的网页地址。。
        String tokenurl = UserConstants.WX_TOKEN_URL
                //replace替换其中一些字段。
                .replace("APPID",UserConstants.APPID)
                .replace("SECRET",UserConstants.SECRET)
                //替换code,前端已经分割了code，这里替换的就是code的值。
                .replace("CODE",code);


        //第三步使用工具类发送请求,使用此工具类需要导入依赖包，这里获取到微信信息。
        String tokens = HttpClientUtils.httpGet(tokenurl);
        //这里需要判断一下，如果我没有请求发送了没有响应呢。
        if (StringUtils.isEmpty(tokens)){
            throw new BusinessException("系统繁忙");
        }
        //转为JSON
        JSONObject jsonObject = JSONObject.parseObject(tokens);
        //我们想要拿到微信令牌里的token和openid,openid是每个微信用户的唯一标识。
        String accessToken = jsonObject.getString("access_token");
        String openId = jsonObject.getString("openid");


        //第四步根据openid查询数据库，如果数据库里没有，那就是用户第一次扫码。因为openid是每一个微信用户的唯一标识。
        WxUser wxUser = wxUserMapper.loadByOpenId(openId);
        //判断   , &&意思是并且， 这里不等于Null,那就证明用户曾经扫码过了，相当于第二次登录。
        if (wxUser != null && wxUser.getUser_id() != null ){
            //不等于Null那就证明用户已经存在直接免密码登录。
            Logininfo logininfo = logininfoMapper.loadByUserID(wxUser.getUser_id());
            //密码一致把数据保存到redis.
            //UUID唯一标识符，全世界都不会重复，保证token不重复，后端拦截要使用。
            String token  = UUID.randomUUID().toString();
            //把数据保存到redis。
            redisTemplate.opsForValue().set(
                    token,   //token随机串，因为登录拦截要使用。。
                    logininfo, //登录用户的信息，这里如果传的是对象就得实现序列化，Serializable。
                    30,   //时间
                    TimeUnit.MINUTES //时间类型，分钟。
            );
            //获取Map
            Map<String,Object> map = new HashMap<>();
            //给token设置Key和值
            map.put("token",token);
            //================我们有些数据不希望前端看到，所以设置为Null再返回到前端。
            logininfo.setPassword(null);
            logininfo.setSalt(null);
            //给用户数据设置key和值。
            map.put("logininfo",logininfo);
            //直接返回给前端。
            return new AjaxResult().setResultObj(map);
        }
        //如果在数据库中不存在，那证明是第一次登录，那就走绑定流程。
            String binderParams = "binder.html?accessToken="+accessToken+"&openId="+openId;
            return new AjaxResult(false,binderParams);


    }





    //微信绑定。
    @Override
    public Map<String, Object> binderWechat(Map<String, String> param) {
       //校验参数。
        String phone = param.get("phone");
        String verifyCode = param.get("verifyCode");
        String accessToken = param.get("accessToken");
        String openId = param.get("openId");
        if (StringUtils.isEmpty(phone) ||StringUtils.isEmpty(verifyCode) || StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId) ){
            throw new RuntimeException("输入完整信息");
        }
        //校验验证码。
        Object o = redisTemplate.opsForValue().get(UserConstants.BUSINESS_TYPE_BINDER + phone);
        if(o == null){
            throw new BusinessException("验证码已失效，请重新获取验证码！");
        }
        //验证码是否一致  不要区分大小写
        String code = o.toString().split(":")[0];
        if(!verifyCode.equalsIgnoreCase(code)){
            throw new BusinessException("验证码错误");
        }
        //查看是否已经注册过了。
        User user= userMapper.loadByPhone(phone);
        Logininfo logininfo = null;
        if (user != null){
            //如果注册过了，直接绑定微信信息，再直接绑定。
            logininfo = logininfoMapper.loadByUserID(user.getId());

        }else {
            //如果没有，那么就生成再获取微信再绑定。
            //第六步，上方验证全通过，则可以保存数据，需要保存两张表。
            //第一张。t_user表。
            user = phone2User(phone);
            //第二张表，t_logininfo。
            logininfo = logininfo2user(user);
            //数据库保存t_logininfo的数据。
            logininfoMapper.add(logininfo);
            //数据库保存t_user表，这里需要注意t_user表里有logininfo ID这个字段，需要获取再赋值。
            user.setLogininfo_id(logininfo.getId()); //数据现在已经保存再user里，等待进入数据库,一定要设置返回值id，在xml里ukk。
            userMapper.add(user);
        }
        //查询微信信息，绑定平台账号。
        String wxUserUrl = UserConstants.WX_USER_URL
                .replace("ACCESS_TOKEN",accessToken)
                .replace("OPENID",openId);
        String wxUserstr = HttpClientUtils.httpGet(wxUserUrl);
        //封装WXUser对象绑定平台用户。
        WxUser wxUser = initWxUser(wxUserstr, user);
        wxUserMapper.add(wxUser);
        //直接登录。
        //密码一致把数据保存到redis.
        //UUID唯一标识符，全世界都不会重复，保证token不重复，后端拦截要使用。
        String token  = UUID.randomUUID().toString();
        //把数据保存到redis。
        redisTemplate.opsForValue().set(
                token,   //token随机串，因为登录拦截要使用。。
                logininfo, //登录用户的信息，这里如果传的是对象就得实现序列化，Serializable。
                30,   //时间
                TimeUnit.MINUTES //时间类型，分钟。
        );
        //获取Map
        Map<String,Object> map = new HashMap<>();
        //给token设置Key和值
        map.put("token",token);
        //================我们有些数据不希望前端看到，所以设置为Null再返回到前端。
        logininfo.setPassword(null);
        logininfo.setSalt(null);
        //给用户数据设置key和值。
        map.put("logininfo",logininfo);
        return map;
    }


    private WxUser initWxUser(String wxUserTmp, User user) {
        //装换微信用户信息
        JSONObject jsonObject = JSONObject.parseObject(wxUserTmp);
        WxUser wxUser =  new WxUser();
        wxUser.setUser_id(user.getId());//绑定用户
        wxUser.setOpenid(jsonObject.getString("openid"));
        wxUser.setNickname(jsonObject.getString("nickname"));
        wxUser.setSex(jsonObject.getInteger("sex"));
        wxUser.setAddress(null);
        wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));
        wxUser.setUnionid(jsonObject.getString("unionid"));
        return wxUser;
    }




    //把前端传来的参数，拷贝给User,注：这种方法必须两个表的字段名一致，并且获取的也一致。
    private User phone2User(String phone) {
        User user = new User();
        user.setUsername(phone);
        user.setPhone(phone);
        //盐值
        String salt = StrUtils.getComplexRandomString(32);
        String md5Pwd = MD5Utils.encrypByMd5("1" + salt);
        user.setSalt(salt);
        user.setPassword(md5Pwd);
        return user;
    }


    //把前端传来的参数，拷贝给Logininfo,注：这种方法必须两个表的字段名一致，并且获取的也一致。
    //接收类型为Logininfo，接收对象为user。
    private Logininfo logininfo2user(User user) {
        //获取对象
        Logininfo logininfo = new Logininfo();
        //拷贝,因为两方所需数据一致，user已经获取，Logininfo也只需要这些数据。
        BeanUtils.copyProperties(user,logininfo); //第一个为A，第二个为B，A传B。
        //返回，谁调用我就返回给谁。
        return logininfo;
    }
}
